package com.ytjj.qmyx.mall.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import com.github.binarywang.wxpay.v3.util.SignUtils;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.ytjj.common.exception.ApiException;
import com.ytjj.qmyx.mall.config.MyWxConfig;
import com.ytjj.qmyx.mall.constants.CompanyConstant;
import com.ytjj.qmyx.mall.mapper.OrdersDao;
import com.ytjj.qmyx.mall.mapper.OrdersPayMapper;
import com.ytjj.qmyx.mall.mapper.PayConfigMapper;
import com.ytjj.qmyx.mall.model.OrdersPay;
import com.ytjj.qmyx.mall.model.OrdersPayExample;
import com.ytjj.qmyx.mall.model.PayConfig;
import com.ytjj.qmyx.mall.model.dto.WxPayDto;
import com.ytjj.qmyx.mall.service.WxService;
import com.ytjj.qmyx.mall.utils.WxPayServiceUtil;
import com.ytjj.qmyx.mall.utils.WxpayV3Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.util.*;

/**
 * H5 微信浏览器内部支付
 *
 *
 * @version 1.0
 *
 */
@RefreshScope
@Service("H5-JSAPI")
@Slf4j
public class WxH5JsApiServiceImpl implements WxService<WxPayDto> {

    @Resource
    private OrdersDao ordersDao;
    @Resource
    private OrdersPayMapper ordersPayMapper;
    @Resource
    private PayConfigMapper payConfigMapper;

    public static final String TYPE = "H5";

    public static final String TRADE_TYPE = "JSAPI";

    public static final String COMPANY = "guchuang";
    public static final String COMPANY_NEW = "guchuang";
    public static final String mchType = "NORMAL";

    @Value("${payment.uatUrl:}")
    private String uatUrl;
    @Value("${payment.uatBeforeNotifyUrl:api.XXXX.com}")
    private String uatBeforeNotifyUrl;
    @Value("${payment.uatBeforeRefundNotifyUrl:admin.XXXX.com}")
    private String uatBeforeRefundNotifyUrl;

    /**
     * 支付方式：v2/v3
     */
    @Value("${payment.type:v3}")
    private String paymentType;

    @Override
    public Map<String, String> doUnifiedOrder(WxPayDto wxH5PayDto) throws Exception {
        log.info("wxH5PayDto,{}", JSON.toJSONString(wxH5PayDto));

        if("v3".equals(paymentType)){
            PayConfig payConfig = ordersDao.getPayConfig(COMPANY_NEW, TYPE, TRADE_TYPE, mchType);
            //预发布环境回调地址切换
            if (StringUtils.isNotBlank(uatUrl) && payConfig!=null){
                String notifyUrl = payConfig.getNotifyUrl().replace(uatBeforeNotifyUrl,uatUrl);
                String refundNotifyUrl = payConfig.getRefundNotifyUrl().replace(uatBeforeRefundNotifyUrl,uatUrl);
                payConfig.setNotifyUrl(notifyUrl);
                payConfig.setRefundNotifyUrl(refundNotifyUrl);
                log.info("预发布环境,{},{}",payConfig.getNotifyUrl(),payConfig.getRefundNotifyUrl());
            }
            // 构造请求数据
            JSONObject reqJSON = WxPayServiceUtil.buildV3OrderRequest(wxH5PayDto, payConfig);
            JSONObject payer = new JSONObject();
            payer.put("openid", wxH5PayDto.getOpenid());
            reqJSON.put("payer", payer);

            WxPayService wxPayService= WxPayServiceUtil.getWxPayService(payConfig);
            wxPayService.getConfig().setTradeType(WxPayConstants.TradeType.JSAPI);
            wxPayService.getConfig().setPrivateKeyPath(payConfig.getPrivateKeyPath());
            wxPayService.getConfig().setCertSerialNo(payConfig.getCertSerialNo());
            wxPayService.getConfig().setApiV3Key(payConfig.getWxKey());
            String reqUrl = WxpayV3Util.NORMALMCH_URL_MAP.get(WxPayConstants.TradeType.JSAPI);
            try {
                // 调起上游接口：
                // 1. 如果抛异常，则订单状态为： 生成状态，此时没有查单处理操作。 订单将超时关闭
                // 2. 接口调用成功， 后续异常需进行捕捉， 如果 逻辑代码出现异常则需要走完正常流程，此时订单状态为： 支付中， 需要查单处理。
                log.info("微信请求 {}",JSONObject.toJSONString(reqJSON));
                JSONObject resJSON = WxpayV3Util.unifiedOrderV3(reqUrl, reqJSON, wxPayService.getConfig());
                log.info("微信返回 {}",resJSON);
                Map<String, String> payInfo = new HashMap<>(); // 如果用JsonObject会出现签名错误
                String packageStr = ""+ resJSON.get("package");
                String timestamp = System.currentTimeMillis() / 1000 + "";
                String nonceStr = WXPayUtil.generateNonceStr();
                payInfo.put("appId", payConfig.getAppid());
                payInfo.put("timeStamp", timestamp);
                payInfo.put("nonceStr", nonceStr);
                payInfo.put("package", packageStr);
                payInfo.put("signType", "RSA");

                String beforeSign = String.format("%s\n%s\n%s\n%s\n", payConfig.getAppid(), timestamp, nonceStr, packageStr);
                payInfo.put("sign", SignUtils.sign(beforeSign, PemUtils.loadPrivateKey(new FileInputStream(wxPayService.getConfig().getPrivateKeyPath()))));

                payInfo.put("payConfigId", String.valueOf(payConfig.getId()));
                payInfo.put("redirect_url", payConfig.getRedirectUrl());
                return payInfo;
            }catch (Exception e) {
                log.error("===调用微信支付接口报错,{}",e);
                throw new Exception(e.getMessage());
            }
        }

        PayConfig payConfig = ordersDao.getPayConfigByCompany(wxH5PayDto.getCompany(), TYPE, TRADE_TYPE);
        MyWxConfig myWxConfig = new MyWxConfig(payConfig);


        Map<String, String> data = new HashMap<>();
        data.put("appid", payConfig.getAppid());
        data.put("attach", wxH5PayDto.getToken());
        data.put("body", wxH5PayDto.getBody());
        data.put("mch_id", payConfig.getMchId());
        data.put("nonce_str", WXPayUtil.generateNonceStr());
        data.put("notify_url", payConfig.getNotifyUrl());
        data.put("out_trade_no", wxH5PayDto.getResOrderNo());
        data.put("spbill_create_ip", payConfig.getSpbillCreateIp());
        data.put("total_fee", wxH5PayDto.getRealTotalMoney());
        data.put("openid", wxH5PayDto.getOpenid());
        data.put("trade_type", TRADE_TYPE);

        WXPay wxpay = new WXPay(myWxConfig, WXPayConstants.SignType.MD5);
        data.put("sign", WXPayUtil.generateSignature(data, payConfig.getWxKey(),
                WXPayConstants.SignType.MD5));

        Map<String, String> response = wxpay.unifiedOrder(data);
        log.info(JSONObject.toJSONString(response));
        if ("SUCCESS".equals(response.get("return_code"))) {
            Map<String, String> param = new HashMap<>();
            param.put("appId", payConfig.getAppid());
            param.put("nonceStr", WXPayUtil.generateNonceStr());
            param.put("package", "prepay_id=" + response.get("prepay_id"));
            param.put("signType", "MD5");
            param.put("timeStamp", System.currentTimeMillis() / 1000 + "");
            param.put("sign", WXPayUtil.generateSignature(param, payConfig.getWxKey(),
                    WXPayConstants.SignType.MD5));
            param.put("payConfigId", String.valueOf(payConfig.getId()));
            return param;
        }
        throw new Exception("下单失败");
    }

    @Override
    public Map<String, String> doUnifiedRechargeOrder(WxPayDto wxH5PayDto) throws Exception {
        return null;
    }

    @Override
    public Map<String, String> wxOrderQuery(String resOrderNo) throws Exception {
        if("v3".equals(paymentType)){
            PayConfig payConfig = ordersDao.getPayConfig(COMPANY_NEW, TYPE, TRADE_TYPE, mchType);
            WxPayService wxPayService = WxPayServiceUtil.getWxPayService(payConfig);
            wxPayService.getConfig().setTradeType(WxPayConstants.TradeType.JSAPI);
            wxPayService.getConfig().setPrivateKeyPath(payConfig.getPrivateKeyPath());
            wxPayService.getConfig().setCertSerialNo(payConfig.getCertSerialNo());
            wxPayService.getConfig().setApiV3Key(payConfig.getWxKey());
            String reqUrl = String.format("/v3/pay/transactions/out-trade-no/%s", resOrderNo);
            String query = String.format("?mchid=%s", wxPayService.getConfig().getMchId());

            JSONObject resultJSON = WxpayV3Util.queryOrderV3(reqUrl + query, wxPayService.getConfig());

            log.info("微信H5公众号支付状态查询请求 订单号：{},query: {}", resOrderNo, resultJSON);
            //使用官方API请求预付订单
            Map<String, String> response = new HashMap<>();
            Set<String> set= resultJSON.keySet();
            Iterator it = set.iterator();
            // 遍历jsonObject数据，添加到Map对象
            while (it.hasNext()){
                String key = String.valueOf(it.next());
                response.put(key, resultJSON.getString(key));
            }
            log.info("微信H5公众号支付状态查询响应 订单号：{},response: {}", resOrderNo, JSONObject.toJSONString(response));
            return response;
        }
        PayConfig payConfig = ordersDao.getPayConfigByCompany(CompanyConstant.PLATFORM, TYPE, TRADE_TYPE);
        MyWxConfig myWxConfig = new MyWxConfig(payConfig);

        Map<String, String> data = new HashMap<>();
        data.put("appid", payConfig.getAppid());
        WXPay wxpay = new WXPay(myWxConfig, WXPayConstants.SignType.MD5);

        data.put("mch_id", payConfig.getMchId());
        data.put("out_trade_no", resOrderNo);
        data.put("nonce_str", WXPayUtil.generateNonceStr());
        data.put("sign", WXPayUtil.generateSignature(data, payConfig.getWxKey(),
                WXPayConstants.SignType.MD5));
        log.info("微信H5支付状态查询请求 订单号：{},query: {}", resOrderNo, JSONObject.toJSONString(data));
        //使用官方API请求预付订单
        Map<String, String> response = wxpay.orderQuery(data);
        log.info("微信H5支付状态查询响应 订单号：{},response: {}", resOrderNo, JSONObject.toJSONString(response));
        return response;
    }

    @Override
    public Map<String, String> wxPhoneChargesQuery(String resOrderNo) {
        return null;
    }

    @Override
    public Map refundOrders(String outTradeNo, String outRefundNo, Integer totalFee, Integer refundFee, String refundDesc) throws Exception {
//        PayConfig payConfig = ordersDao.getPayConfigByCompany(CompanyConstant.PLATFORM, TYPE, TRADE_TYPE);
        OrdersPayExample ordersPayExample = new OrdersPayExample();
        ordersPayExample.createCriteria().andResOrderNoEqualTo(outTradeNo).andPayTypeEqualTo(new Byte("2"));
        ordersPayExample.setOrderByClause("id desc");
        List<OrdersPay> ordersPays = ordersPayMapper.selectByExample(ordersPayExample);
        PayConfig payConfig = null;
        if (!CollectionUtils.isEmpty(ordersPays)) {
            payConfig = payConfigMapper.selectByPrimaryKey(ordersPays.get(0).getPayConfigId());
        }
        if (null == payConfig) {
            throw new ApiException("退款失败，请联系管理员！！！");
        }
        if("v3".equals(paymentType)){
            JSONObject reqJSON = new JSONObject();
            reqJSON.put("out_trade_no", outTradeNo);   // 订单号
            reqJSON.put("out_refund_no", outRefundNo); // 退款订单号
            reqJSON.put("reason",refundDesc);
            JSONObject amountJson = new JSONObject();
            amountJson.put("refund", refundFee);// 退款金额
            amountJson.put("total", totalFee);// 订单总金额
            amountJson.put("currency", "CNY");// 币种
            reqJSON.put("amount", amountJson);
            WxPayService wxPayService = WxPayServiceUtil.getWxPayService(payConfig);
            wxPayService.getConfig().setKeyPath(payConfig.getCertPath());// 证书路径
            wxPayService.getConfig().setPrivateKeyPath(payConfig.getPrivateKeyPath());
            wxPayService.getConfig().setCertSerialNo(payConfig.getCertSerialNo());
            wxPayService.getConfig().setApiV3Key(payConfig.getWxKey());
            JSONObject resultJSON = WxpayV3Util.refundV3(reqJSON, wxPayService.getConfig());
            log.info("退款结果{}",resultJSON.toJSONString());
            String status = resultJSON.getString("status");
            Map<String, String> response = new HashMap<>();
            response.put("status",status);
            response.put("return_code",status);
            if("SUCCESS".equals(status)){ // 退款成功
                String refundId = resultJSON.getString("refund_id");
                response.put("out_refund_no",resultJSON.getString("out_refund_no"));
                response.put("refund_id",resultJSON.getString("refund_id"));
                response.put("out_trade_no",resultJSON.getString("out_trade_no"));
                response.put("result_code","SUCCESS");
            }else if ("PROCESSING".equals(status)){ // 退款处理中
                String refundId = resultJSON.getString("refund_id");
                response.put("out_refund_no",resultJSON.getString("out_refund_no"));
                response.put("refund_id",resultJSON.getString("refund_id"));
                response.put("out_trade_no",resultJSON.getString("out_trade_no"));
                response.put("result_code","PROCESSING");
            }else{
                throw new Exception("发起退款失败");
            }
            log.info("微信发起退款：response:{}",JSONObject.toJSONString(response));
            return response;
        }

        MyWxConfig myWxConfig = new MyWxConfig(payConfig);

        Map<String, String> data = new HashMap<>();
        //生成商户订单号，不可重复
        WXPay wxpay = new WXPay(myWxConfig, WXPayConstants.SignType.MD5);
        data.put("appid", payConfig.getAppid());
        data.put("mch_id", payConfig.getMchId());
        data.put("nonce_str", WXPayUtil.generateNonceStr());
        data.put("out_trade_no", outTradeNo);
        data.put("out_refund_no", outRefundNo);
        data.put("total_fee", String.valueOf(totalFee));
        data.put("refund_fee", String.valueOf(refundFee));
//        data.put("refund_desc", refundDesc);
//        data.put("notify_url",payConfig.getRefundNotifyUrl());
        data.put("sign", WXPayUtil.generateSignature(data, payConfig.getWxKey(),
                WXPayConstants.SignType.MD5));
        Map<String, String> response = wxpay.refund(data);
        log.info("微信发起退款：response:{}",JSONObject.toJSONString(response));
        return response;
    }

    @Override
    public Map refundRecharge(String outTradeNo, String outRefundNo, Integer totalFee, Integer refundFee, String refundDesc) throws Exception {
        PayConfig payConfig = ordersDao.getPayConfigByCompany(CompanyConstant.PLATFORM, TYPE, TRADE_TYPE);
        MyWxConfig myWxConfig = new MyWxConfig(payConfig);

        Map<String, String> data = new HashMap<>();
        //生成商户订单号，不可重复
        WXPay wxpay = new WXPay(myWxConfig, WXPayConstants.SignType.MD5);
        data.put("appid", payConfig.getAppid());
        data.put("mch_id", payConfig.getMchId());
        data.put("nonce_str", WXPayUtil.generateNonceStr());
        data.put("out_trade_no", outTradeNo);
        data.put("out_refund_no", outRefundNo);
        data.put("total_fee", String.valueOf(totalFee));
        data.put("refund_fee", String.valueOf(refundFee));
//        data.put("refund_desc", refundDesc);
        data.put("notify_url",payConfig.getRefundNotifyUrl());
        data.put("sign", WXPayUtil.generateSignature(data, payConfig.getWxKey(),
                WXPayConstants.SignType.MD5));
        Map<String, String> response = wxpay.refund(data);
        log.info("微信发起退款：response:{}",JSONObject.toJSONString(response));
        return response;
    }
}
